package com.cjh.simple.HttpProxy;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * https://blog.csdn.net/u011995362/article/details/53729512
 *
 */
public class App
{
    public static ExecutorService pool = Executors.newCachedThreadPool();
    
    public static void main(String[] args) throws IOException
    {
        @SuppressWarnings("resource")
        ServerSocket server = new ServerSocket(8008);
        while (true)
        {
            // 对应Https连接建立过程第一步,接收浏览器向代理建立的Socket连接
            Socket client = server.accept();
            pool.execute(new Worker(client));
        }
        
    }
    
    public static class Worker implements Runnable
    {
        // http协议内容中的CRLF
        private static final String CRLF = "\r\n";
        private Socket              client;
        
        Worker(Socket client)
        {
            this.client = client;
        }
        
        @Override
        public void run()
        {
            String serverAddr = null;
            int serverPort = 80;
            try (InputStream clientIs = client.getInputStream();
                    OutputStream clientOs = client.getOutputStream();)
            {
                // 默认只读取前100K,假定所有的头部都不超过100K就可以解析到Host头部.这块也是不好的地方
                // 获取请求行,根据HTTP协议规范,第一行为请求行.
                byte[] data = new byte[102400];
                int length = clientIs.read(data);
                String input = null;
                String reqLine = null;
                if (length > 0)
                {
                    input = new String(data, 0, length);
                    reqLine = input.substring(0, input.indexOf(CRLF));
                }
                if (reqLine == null)
                {
                    client.close();
                }
                System.out.println("请求行：" + reqLine);
                String[] params = reqLine.split(" ");
                String reqMethod = params[0];
                String reqUrl = params[1];
                String transferUrl = getUrl(input, reqUrl);
                if (transferUrl.indexOf(":") > 0)
                {
                    serverAddr = transferUrl.substring(0, transferUrl.indexOf(":"));
                    serverPort = Integer.parseInt(transferUrl.substring(transferUrl.indexOf(":") + 1));
                }
                else
                {
                    serverAddr = transferUrl;
                    serverPort = 80;
                }
                // 接下来开始真正的代理转发逻辑.
                try (Socket serverClient = new Socket(serverAddr, serverPort);
                        InputStream serverIs = serverClient.getInputStream();
                        OutputStream serverOs = serverClient.getOutputStream();)
                {
                    // 判断是否是CONNECT方法
                    if (reqMethod.equalsIgnoreCase("CONNECT"))
                    {
                        String proxyResponse = "HTTP/1.1 200 Connection Established" + CRLF + "Proxy-agent: Anyone-BlindProxyServer/1.0" + CRLF + CRLF;
                        clientOs.write(proxyResponse.getBytes());
                        clientOs.flush();
                    }
                    else
                    {
                        // 其他的HTTP方法,将读取到的数据原封不动的发送到Server端.
                        serverOs.write(data, 0, length);
                        serverOs.flush();
                    }
                    if (!client.isInputShutdown() && !serverClient.isInputShutdown())
                    {
                        CountDownLatch latch = new CountDownLatch(2);
                        // 将客户端的输入流管道输入到server sock的输出流.
                        // client to server.
                        pool.execute(new PipeThread(client, serverClient, clientIs, serverOs, latch));
                        // server to clien.
                        pool.execute(new PipeThread(serverClient, client, serverIs, clientOs, latch));
                        latch.await();
                    }
                }
            }
            catch (Exception e)
            {
                e.printStackTrace();
                System.out.println("failed to connect to server[" + serverAddr + "] : [" + serverPort + "]...");
            }
            finally
            {
                try
                {
                    client.close();
                }
                catch (IOException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        
        class PipeThread implements Runnable
        {
            private final Socket         client;
            private final Socket         server;
            private final InputStream    is;
            private final OutputStream   os;
            private final CountDownLatch latch;
            
            public PipeThread(Socket client, Socket server, InputStream is, OutputStream os, CountDownLatch latch)
            {
                this.client = client;
                this.server = server;
                this.is = is;
                this.os = os;
                this.latch = latch;
            }
            
            @Override
            public void run()
            {
                try
                {
                    byte[] data = new byte[1024000];
                    int length = -1;
                    while (!client.isInputShutdown())
                    {
                        if ((length = is.read(data)) != -1)
                        {
                            os.write(data, 0, length);
                            os.flush();
                        }
                        else
                        {
                            // client端已经关闭了输出流.
                            server.shutdownOutput();
                            client.shutdownInput();
                        }
                    }
                }
                catch (Exception e)
                {
                }
                finally
                {
                    latch.countDown();
                }
            }
        }
        
        private String getUrl(String input, String reqUrl)
        {
            String serverUrl = null;
            // 解析远端主机.
            if (reqUrl.startsWith("/"))
            {
                int location = 0;
                while ((location = input.indexOf(CRLF)) > 0)
                {
                    String header = input.substring(0, location);
                    // 根据HTTP协议规范,如果请求行中使用相对路径,则Web Server主机由Host头部决定.
                    // 需要注意HTTP协议规范中是大小写不敏感的,所以需要使用equalsIgnoreCase
                    if (header.length() > 5 && header.substring(0, 5).equalsIgnoreCase("Host:"))
                    {
                        String host = header.substring("Host:".length(), header.length());
                        serverUrl = host;
                        break;
                    }
                    input = input.substring(location + CRLF.length());
                }
            }
            else
            {
                // 根据HTTP协议规范,如果请求行中使用绝对路径,则Web
                // Server主机是URL的一部分,且必须忽略Host头部.
                serverUrl = reqUrl;
            }
            // url: http://www.baidu.com/index.html
            if (serverUrl.indexOf("//") > 0)
            {
                serverUrl = serverUrl.substring(serverUrl.indexOf("//") + 2);
            }
            // 去除URL中的路径只保留域名: www.baidu.com/index.html
            if (serverUrl.indexOf("/") > 0)
            {
                serverUrl = serverUrl.substring(0, serverUrl.indexOf("/"));
            }
            return serverUrl;
        }
    }
    
}
